package com.moyq5.mvc.framework.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.moyq5.mvc.framework.general.Page;
import com.moyq5.mvc.framework.general.Paging;
import com.moyq5.mvc.framework.general.Sort;
import com.moyq5.mvc.framework.general.Sorting;
import com.moyq5.mvc.framework.utils.EntityUtils;

/**
 * @author Moyq5
 * @date 2018年8月8日
 * @param <T>
 */
public abstract class BasicDao<T,S extends T> extends ExampleDao<T> implements Dao<T, S> {
	
	private Class<T> clazz;
	private String PREFIX;

	public BasicDao(Class<T> clazz) {
		super(clazz);
		this.clazz = clazz;
		this.PREFIX = clazz.getSimpleName() + "_";
	}
	
	@Override
	public T one(Serializable id) {
		return em().find(clazz, id);
	}

	@Override
	public T one(S t4s) {
		Paging pi = new Paging();
		pi.setPageSize(1);
		List<T> list = page(t4s, pi).getList();
        return null != list && list.size() > 0 ? list.get(0): null;
	}

	@Override
	public T one(S t4s, String searchWord) {
		Paging pi = new Paging();
		pi.setPageSize(1);
		pi.setSearchWord(searchWord);
		List<T> list = page(t4s, pi).getList();
        return null != list && list.size() > 0 ? list.get(0): null;
	}
	
	@Override
	public T one(S t4s, List<Sort> sorts) {
		Paging pi = new Paging();
		pi.setPageSize(1);
		pi.setSorts(sorts);
		List<T> list = page(t4s, pi).getList();
        return null != list && list.size() > 0 ? list.get(0): null;
	}

	@Override
	public T one(S t4s, Sorting si) {
		Paging pi = new Paging();
		pi.setPageSize(1);
		pi.setSorts(si.getSorts());
		pi.setSearchWord(si.getSearchWord());
		List<T> list = page(t4s, pi).getList();
        return null != list && list.size() > 0 ? list.get(0): null;
	}
	
	@Override
	public long count(S t4s) {
		CriteriaQuery<Long> cq = createCountQuery(t4s, null);
		TypedQuery<Long> tq = em().createQuery(cq);
		return tq.getSingleResult();
	}

	@Override
	public long count(S t4s, String searchWord) {
		CriteriaQuery<Long> cq = createCountQuery(t4s, searchWord);
		TypedQuery<Long> tq = em().createQuery(cq);
		return tq.getSingleResult();
	}

	@Override
	public List<T> list(S t4s) {
		Sorting si = new Sorting();
		return list(t4s, si);
	}

	@Override
	public List<T> list(S t4s, String searchWord) {
		Sorting si = new Sorting();
		si.setSearchWord(searchWord);
		return list(t4s, si);
	}
	
	@Override
	public List<T> list(S t4s, List<Sort> sorts) {
		Sorting si = new Sorting();
		si.setSorts(sorts);
		return list(t4s, si);
	}
	
	@Override
	public List<T> list(S t4s, Sorting si) {
		CriteriaQuery<T> cq = createSearchCriteria(t4s, si.getSearchWord());
		cq = sort(cq, si.getSorts());
		return em().createQuery(cq).getResultList();
	}
	
	@Override
	public Page<T> page(S t4s) {
		Paging pi = new Paging();
		return page(t4s, pi);
	}

	@Override
	public Page<T> page(S t4s, String searchWord) {
		Paging pi = new Paging();
		pi.setSearchWord(searchWord);
		return page(t4s, pi);
	}
	
	@Override
	public Page<T> page(S t4s, List<Sort> sorts) {
		Paging pi = new Paging();
		pi.setSorts(sorts);
		return page(t4s, pi);
	}

	@Override
	public Page<T> page(S t4s, Sorting si) {
		Paging pi = new Paging();
		pi.setSorts(si.getSorts());
		pi.setSearchWord(si.getSearchWord());
		return page(t4s, pi);
	}
	
	@Override
	public Page<T> page(S t4s, Paging pi) {
		CriteriaQuery<T> cq = createSearchCriteria(t4s, pi.getSearchWord());
		
		Page<T> page = new Page<T>();
		page.setPaging(pi);
		
		// 统计
		if (null != pi && pi.getIsCount()) {
			pi.setTotalResult(count(cq));
			// 无记录
			if (pi.getTotalResult() == 0) {
				return page;
			}
		}
		
		// 排序
		cq = sort(cq, pi.getSorts());
				
		TypedQuery<T> tq = em().createQuery(cq);
		
		// 分页
		if (null != pi) {
			tq.setFirstResult((int)pi.getFirstResult());
			// firstResult + pageSize 超有有效记录范围会出现性能问题
			if (pi.getIsCount() && pi.getTotalResult() < pi.getFirstResult() + pi.getPageSize()) {
				tq.setMaxResults((int)(pi.getTotalResult() - pi.getFirstResult()));
			} else {
				tq.setMaxResults(pi.getPageSize());
			}
		}
		page.setList(tq.getResultList());
		return page;
	}

	@Override
	public T merge(T entity) {
		return em().merge(entity);
	}

	@Override
	public T persist(T entity) {
	    if (clazz != entity.getClass() && entity.getClass().getSuperclass().isAssignableFrom(clazz)) {
	        try {
                T en = clazz.newInstance();
                EntityUtils.combine(entity, en);
                em().persist(en);
                return en;
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
	    }
		em().persist(entity);
		return entity;
	}

	@Override
	public void remove(T entity) {
		em().remove(entity);
	}
	
	protected int executeUpdate(final String queryName, Map<String, Object> params) {
		return createQuery(queryName, params).executeUpdate();
	}
	
	protected T one(final String queryName, Map<String, Object> params) {
		TypedQuery<T> q = createQuery(queryName, params);
		q.setMaxResults(1);
		return q.getSingleResult();
	}
	
	protected List<T> list(final String queryName, Map<String, Object> params) {
		TypedQuery<T> q = createQuery(queryName, params);
		return q.getResultList();
	}
	
	protected CriteriaQuery<T> createSearchCriteria(ExampleQuery<T> eq, S t4s) {
		return eq.getCriteriaQuery();
	}
	
	protected final CriteriaQuery<T> createSearchCriteria(S t4s, Paging si) {
		ExampleQuery<T> eq = createExampleQuery(null, t4s, si.getSearchWord());
		return createSearchCriteria(eq, t4s);
	}
	
	protected final Long count(CriteriaQuery<?> cq) {
		CriteriaQuery<Long> ccq = createCountQuery(cq);
		TypedQuery<Long> tq = em().createQuery(ccq);
		return tq.getSingleResult();
	}
	
	private CriteriaQuery<Long> createCountQuery(S t4s, String searchWord) {
		ExampleQuery<T> eq = createExampleQuery(null, t4s, searchWord);
		CriteriaQuery<T> cq = createSearchCriteria(eq, t4s);
		return createCountQuery(cq);
	}
	
	private CriteriaQuery<T> createSearchCriteria (S t4s, String searchWord) {
		ExampleQuery<T> eq = createExampleQuery(null, t4s, searchWord);
		return createSearchCriteria(eq, t4s);
	}
	
	private CriteriaQuery<T> sort(CriteriaQuery<T> cq, List<Sort> sorts) {
		if (null == sorts || sorts.size() == 0) {
			return cq;
		}
		CriteriaBuilder cb = em().getCriteriaBuilder();
		List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
		Root<?> r = cq.getRoots().iterator().next();
		for (Sort item : sorts) {
			String[] fields = item.getField().split("\\.{1}");
			Path<?> path = null;
			for (String field: fields) {
				path = null == path ? r.get(field): path.get(field);
			}
			javax.persistence.criteria.Order order = null;
			if (item.getIsDesc()) {
				order = cb.desc(path);
			} else {
				order = cb.asc(path);
			}
			orderList.add(order);
		}
		cq.orderBy(orderList);
		return cq;
	}
	
	private <E> CriteriaQuery<Long> createCountQuery(CriteriaQuery<E> cq) {
		CriteriaBuilder cb = em().getCriteriaBuilder();
		CriteriaQuery<Long> ccq = cb.createQuery(Long.class);
		Root<?> r = ccq.from(cq.getResultType());
		r.alias(cq.getRoots().iterator().next().getAlias());
		ccq.select(cb.count(cb.literal(1)));
		Predicate predicate = cq.getRestriction();
		if (null != predicate && predicate.getExpressions().size() > 0) {
			ccq.where(predicate);
		}
		return ccq;
	}
	
	private TypedQuery<T> getNamedQuery(final String queryName) {
		return em().createNamedQuery(PREFIX + queryName, clazz);
	}
	
	private TypedQuery<T> createQuery(final String queryName, Map<String, Object> params) {
		TypedQuery<T> q = getNamedQuery(queryName);
		params.forEach((k, v) -> {
			q.setParameter(k, v);
		});
		return q;
	}

	@Override
	public void flush() {
		em().flush();
	}
	
	
	
}
